﻿using SiMay.Sockets.Delegate;
using SiMay.Sockets.Tcp.Awaitable;
using SiMay.Sockets.Tcp.Pooling;
using SiMay.Sockets.Tcp.TcpConfiguration;
using SiMay.Sockets.UtilityHelper;
using System;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;

namespace SiMay.Sockets.Tcp.Session
{
    public class TcpSocketSaeaPackBased : TcpSocketSaeaSession
    {
        /// <summary>
        /// 最后一次接收数据的时间
        /// </summary>
        internal DateTime LastReceiveDataTime { get; private set; } = DateTime.Now;

        /// <summary>
        /// 标准心跳包数据，用于心跳包
        /// </summary>
        private readonly byte[] _emptyHeart = new byte[] { 0, 0, 0, 0 };

        /// <summary>
        /// 操作锁
        /// </summary>
        private readonly object _opsLock = new object();

        /// <summary>
        /// 数据缓冲区中操作开始位置处的偏移量，以字节为单位
        /// </summary>
        private int _packageRecvOffset;

        /// <summary>
        /// 是否压缩数据
        /// </summary>
        private bool _isCompress;

        /// <summary>
        /// 待发送的数据条数
        /// </summary>
        internal int _intervalIsUseChannel = 0;

        /// <summary>
        /// 报错数据长度的数据头缓存
        /// </summary>
        private byte[] _headBuffer = new byte[4];

        internal TcpSocketSaeaPackBased(
            TcpSocketConfigurationBase configuration,
            SaeaAwaiterPool handlerSaeaPool,
            SessionPool sessionPool,
            NotifyEventHandler<TcpSocketCompletionNotify, TcpSocketSaeaSession> notifyEventHandler,
            TcpSocketSaeaEngineBased agent)
            : base(notifyEventHandler, configuration, handlerSaeaPool, sessionPool, agent)
        {
            _isCompress = configuration.CompressTransferFromPacket;
        }

        /// <summary>
        /// 增加Session
        /// </summary>
        /// <param name="socket"></param>
        /// <exception cref="ArgumentNullException"></exception>
        internal override void Attach(Socket socket)
        {
            if (socket == null)
                throw new ArgumentNullException("socket");

            lock (_opsLock)
            {
                this.LastReceiveDataTime = DateTime.Now;
                //设置为已连接
                this.State = TcpSocketConnectionState.Connected;
                //连接对应的Socekt
                this.Socket = socket;
                //记录连接成功时间
                this.StartTime = DateTime.Now;
                this.SetSocketOptions();
            }
        }

        /// <summary>
        /// 清除Session信息
        /// </summary>
        internal override void Detach()
        {
            lock (_opsLock)
            {
                this.Socket = null;
                this.State = TcpSocketConnectionState.None;
                this.AppTokens = null;
                this._packageRecvOffset = 0;//数据缓冲区中操作开始位置处的偏移量恢复0
            }
        }

        /// <summary>
        /// 设置Socket对象，包括缓存区大小，超时时间，是否使用KeepAlive等
        /// </summary>
        private void SetSocketOptions()
        {
            Socket.ReceiveBufferSize = Configuration.ReceiveBufferSize;
            Socket.SendBufferSize = Configuration.SendBufferSize;
            Socket.ReceiveTimeout = (int)Configuration.ReceiveTimeout.TotalMilliseconds;
            Socket.SendTimeout = (int)Configuration.SendTimeout.TotalMilliseconds;
            Socket.NoDelay = Configuration.NoDelay;

            //是否发送心跳包保持连接
            if (Configuration.KeepAlive)
                SetKeepAlive(Socket, 1, Configuration.KeepAliveInterval, Configuration.KeepAliveSpanTime);

            //设置是否可以重用套接字
            Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, Configuration.ReuseAddress);
        }

        /// <summary>
        /// 操作系统心跳包
        /// </summary>
        /// <param name="sock"></param>
        /// <param name="op"></param>
        /// <param name="interval">多长时间后开始第一次探测（单位：毫秒）</param>
        /// <param name="spantime">探测时间间隔（单位：毫秒）</param>
        private void SetKeepAlive(Socket sock, byte op, int interval, int spantime)
        {
            uint dummy = 0;
            byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
            BitConverter.GetBytes(op).CopyTo(inOptionValues, 0);//开启keepalive
            BitConverter.GetBytes((uint)interval).CopyTo(inOptionValues, Marshal.SizeOf(dummy));//多长时间开始第一次探测
            BitConverter.GetBytes((uint)spantime).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);//探测时间间隔

            sock.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
        }

        /// <summary>
        /// 取出一个异步操作对象，设置4个字节的缓冲区，用来接收数据总长度
        /// </summary>
        internal override void StartProcess()
        {
            var awaiter = HandlerSaeaPool.Take();

            //设置一个4字节的缓冲区，用于接收数据长度(整形为4个字节)
            awaiter.Saea.SetBuffer(_headBuffer, _packageRecvOffset, _headBuffer.Length);
            //等待接收数据
            SaeaExHelper.ReceiveAsync(Socket, awaiter, HeadProcess);
        }

        /// <summary>
        /// 异步接收到数据的回调方法
        /// </summary>
        /// <param name="awaiter">异步操作对象</param>
        /// <param name="error">异步操作结果</param>
        private void HeadProcess(SaeaAwaiter awaiter, SocketError error)
        {
            if (awaiter.Saea.BytesTransferred == 0 || error != SocketError.Success || State != TcpSocketConnectionState.Connected || Socket == null)
            {
                LogHelper.WriteLog("session_recv endtransfer state：" + State.ToString() + " socket_error：" + error.ToString());
                this.EndTransfer(awaiter);
                return;
            }

            //记录最后一次接收数据的时间
            LastReceiveDataTime = DateTime.Now;

            ///获取在套接字操作中传输的字节数
            _packageRecvOffset += awaiter.Saea.BytesTransferred;
            if (_packageRecvOffset >= 4)
            {
                //获取数据总大小
                int packBytesTransferred = BitConverter.ToInt32(_headBuffer, 0);

                //长度包越界判断
                if (packBytesTransferred < 0 || packBytesTransferred > Configuration.SendBufferSize)
                {
                    this.Close(true);
                    return;
                }
                 
                if (packBytesTransferred == 0)
                {
                    //如果是服务端，则反馈心跳包
                    if (this.Configuration._intervalWhetherService)
                        //如果发送数据队列里没有待发送数据
                        if (this._intervalIsUseChannel == 0)
                        {
                            var h_saea = HandlerSaeaPool.Take();

                            //4个字节空包头
                            h_saea.Saea.SetBuffer(_emptyHeart, 0, _emptyHeart.Length);
                            //发送4个字节空包头数据
                            SaeaExHelper.SendAsync(this.Socket, h_saea, (a, e) => HandlerSaeaPool.Return(a));
                        }

                    //跳过数据表长度部分，继续接收数据
                    _packageRecvOffset = 0;
                    awaiter.Saea.SetBuffer(_packageRecvOffset, _headBuffer.Length);
                    //继续等待接收数据
                    SaeaExHelper.ReceiveAsync(this.Socket, awaiter, HeadProcess);
                    return;
                }
                CompletedBuffer = new byte[packBytesTransferred];
                _packageRecvOffset = 0;
                //将缓冲区设置为数据包的完整大小后重新接收数据
                awaiter.Saea.SetBuffer(CompletedBuffer, _packageRecvOffset, CompletedBuffer.Length);
                SaeaExHelper.ReceiveAsync(this.Socket, awaiter, PacketPartProcess);
            }
            else
            {
                awaiter.Saea.SetBuffer(_packageRecvOffset, _headBuffer.Length - _packageRecvOffset);
                SaeaExHelper.ReceiveAsync(this.Socket, awaiter, HeadProcess);
            }
        }

        /// <summary>
        /// 触发接收正式数据
        /// </summary>
        /// <param name="awaiter">异步操作对象</param>
        /// <param name="error">异步操作结果</param>
        private void PacketPartProcess(SaeaAwaiter awaiter, SocketError error)
        {
            int bytesTransferred = awaiter.Saea.BytesTransferred;
            if (bytesTransferred == 0 || error != SocketError.Success || State != TcpSocketConnectionState.Connected || Socket == null)
            {
                LogHelper.WriteLog("session_recv endtransfer state：" + State.ToString() + " socket_error：" + error.ToString());
                this.EndTransfer(awaiter);
                return;
            }

            //接收数据的长度
            this.ReceiveBytesTransferred = bytesTransferred;
            
            //触发接收数据中的回调方法
            this.NotifyEventHandler?.Invoke(TcpSocketCompletionNotify.OnDataReceiveing, this);

            
            LastReceiveDataTime = DateTime.Now;
            _packageRecvOffset += bytesTransferred;

            //判断接收的数据长度是否大于数据总长度
            if (_packageRecvOffset >= CompletedBuffer.Length)
            {
                //触发数据接收完成事件
                this.PackageProcess();

                //重新等待接收数据总长度头
                _packageRecvOffset = 0;
                awaiter.Saea.SetBuffer(_headBuffer, _packageRecvOffset, _headBuffer.Length);
                SaeaExHelper.ReceiveAsync(this.Socket, awaiter, HeadProcess);
            }
            else
            {
                //设置缓存，偏移量为已接收的数据
                awaiter.Saea.SetBuffer(_packageRecvOffset, CompletedBuffer.Length - _packageRecvOffset);
                SaeaExHelper.ReceiveAsync(this.Socket, awaiter, PacketPartProcess);
            }
        }

        /// <summary>
        /// 触发接收数据完成的回调方法
        /// </summary>
        private void PackageProcess()
        {
            if (this._isCompress)
            {
                //解压数据
                CompletedBuffer = DataCompressHelper.DataDecompress(CompletedBuffer);
            }                
            //接收完成回调
            this.NotifyEventHandler?.Invoke(TcpSocketCompletionNotify.OnDataReceived, this);
        }

        /// <summary>
        /// 断开连接，清除异步操作池和Session池
        /// </summary>
        /// <param name="awaiter"></param>
        private void EndTransfer(SaeaAwaiter awaiter)
        {
            this.Close(true);
            this.HandlerSaeaPool.Return(awaiter);
            this.SessionPools.Return(this);
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="data">数据</param>
        public override void SendAsync(byte[] data) => this.SendAsync(data, 0, data.Length);

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="offset">开始位置</param>
        /// <param name="lenght">发送数据长度</param>
        public override void SendAsync(byte[] data, int offset, int lenght)
        {
            if (this.Socket == null)
                return;

            //如果启用数据压缩，压缩数据
            byte[] buffer = this._isCompress
                ? this.BuilderPack(DataCompressHelper.DataCompress(data))
                : this.BuilderPack(data, offset, lenght);

            //取出异步操作对象
            var awaiter = HandlerSaeaPool.Take();
            //将要数据设置为异步发送数据的缓冲区
            awaiter.Saea.SetBuffer(buffer, 0, buffer.Length);

            //发送前增加_intervalIsUseChannel的值
            Interlocked.Increment(ref _intervalIsUseChannel);
            SaeaExHelper.SendAsync(this.Socket, awaiter, (a, e) =>
             {
                 //发送结束减少_intervalIsUseChannel的值
                 Interlocked.Decrement(ref _intervalIsUseChannel);

                 this.HandlerSaeaPool.Return(awaiter);
                 //当前发送数据缓冲区大小
                 this.SendTransferredBytes = buffer.Length;
                 //回调发送成功
                 this.NotifyEventHandler?.Invoke(TcpSocketCompletionNotify.OnSend, this);
             });

        }

        /// <summary>
        /// 创建新数据包，前面为数据包长度，后面为数据
        /// </summary>
        /// <param name="data">数据</param>
        /// <returns></returns>
        private byte[] BuilderPack(byte[] data)
            => this.BuilderPack(data, 0, data.Length);

        /// <summary>
        /// 创建新数据包，前面为数据包长度，后面为数据
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="offset">开始位置</param>
        /// <param name="lenght">数据长度</param>
        /// <returns></returns>
        private byte[] BuilderPack(byte[] data, int offset, int lenght)
        {
            byte[] buffer = new byte[data.Length + sizeof(Int32)];
            byte[] bodyLen = BitConverter.GetBytes(lenght);
            Array.Copy(bodyLen, 0, buffer, 0, bodyLen.Length);
            Array.Copy(data, offset, buffer, bodyLen.Length, lenght);

            return buffer;
        }

        /// <summary>
        /// 关闭连接
        /// </summary>
        /// <param name="notify"></param>
        public override void Close(bool notify)
        {
            lock (_opsLock)
            {
                if (Socket != null)
                {
                    if (notify)
                    {
                        this.NotifyEventHandler?.Invoke(TcpSocketCompletionNotify.OnClosed, this);
                    }

                    try
                    {
                        Socket.Shutdown(SocketShutdown.Both);
                        Socket.Close();
                    }
                    catch (Exception e)
                    {
                        LogHelper.WriteLog("session--close info：" + e.Message);
                    }
                    finally
                    {
                        Socket = null;
                    }
                    State = TcpSocketConnectionState.Closed;

                   
                }
            }
        }
    }


}
